import Quill from 'quill';
import type {
  Props,
  TableCell,
  TableCellBlock,
  TableContainer,
  TableHeader,
  TableList,
  TableMenus,
  UseLanguageHandler
} from '../types';
import eraseIcon from '../assets/icon/erase.svg';
import downIcon from '../assets/icon/down.svg';
import paletteIcon from '../assets/icon/palette.svg';
import saveIcon from '../assets/icon/check.svg';
import closeIcon from '../assets/icon/close.svg';
import { getProperties } from '../config';
import {
  addDimensionsUnit,
  createTooltip,
  getClosestElement,
  getComputeSelectedCols,
  getCorrectBounds,
  getCorrectContainerWidth,
  getCorrectWidth,
  isDimensions,
  isValidColor,
  setElementProperty,
  setElementAttribute
} from '../utils';
import { ListContainer } from '../formats/list';
import iro from '@jaames/iro';

interface Child {
  category: string;
  propertyName: string;
  value?: string;
  attribute?: Props;
  options?: string[];
  tooltip?: string;
  menus?: Menus[];
  valid?: (value?: string) => boolean;
  message?: string;
}

interface Menus {
  icon: string;
  describe: string;
  align: string;
}

interface Properties {
  content: string;
  children: Child[];
}

interface Options {
  type: string;
  attribute: Props;
}

interface ColorList {
  value: string;
  describe: string;
}

const ACTION_LIST = [
  { icon: saveIcon, label: 'save', type: 'button' },
  { icon: closeIcon, label: 'cancel', type: 'button' }
];

const COLOR_LIST: ColorList[] = [
  { value: '#000000', describe: 'black' },
  { value: '#4d4d4d', describe: 'dimGrey' },
  { value: '#808080', describe: 'grey' },
  { value: '#e6e6e6', describe: 'lightGrey' },
  { value: '#ffffff', describe: 'white' },
  { value: '#ff0000', describe: 'red' },
  { value: '#ffa500', describe: 'orange' },
  { value: '#ffff00', describe: 'yellow' },
  { value: '#99e64d', describe: 'lightGreen' },
  { value: '#008000', describe: 'green' },
  { value: '#7fffd4', describe: 'aquamarine' },
  { value: '#40e0d0', describe: 'turquoise' },
  { value: '#4d99e6', describe: 'lightBlue' },
  { value: '#0000ff', describe: 'blue' },
  { value: '#800080', describe: 'purple' }
];

class TablePropertiesForm {
  tableMenus: TableMenus;
  options: Options;
  attrs: Props;
  borderForm: HTMLElement[];
  saveButton: HTMLButtonElement;
  form: HTMLDivElement;
  constructor(tableMenus: TableMenus, options?: Options) {
    this.tableMenus = tableMenus;
    this.options = options;
    this.attrs = { ...options.attribute };
    this.borderForm = [];
    this.saveButton = null;
    this.form = this.createPropertiesForm(options);
  }

  checkBtnsAction(status: string) {
    if (status === 'save') {
      this.saveAction(this.options.type);
    }
    this.removePropertiesForm();
    this.tableMenus.showMenus();
    this.tableMenus.updateMenus();
  }

  createActionBtns(listener: EventListener, showLabel: boolean) {
    const useLanguage = this.getUseLanguage();
    const container = document.createElement('div');
    const fragment = document.createDocumentFragment();
    container.classList.add('properties-form-action-row');
    for (const { icon, label, type } of ACTION_LIST) {
      const button = document.createElement('button');
      const iconContainer = document.createElement('span');
      iconContainer.innerHTML = icon;
      button.appendChild(iconContainer);
      setElementAttribute(button, { label, type });
      if (showLabel) {
        const labelContainer = document.createElement('span');
        labelContainer.innerText = useLanguage(label);
        button.appendChild(labelContainer);
      }
      fragment.appendChild(button);
    }
    container.addEventListener('click', e => listener(e));
    container.appendChild(fragment);
    return container;
  }

  createCheckBtns(child: Child) {
    const { menus, propertyName } = child;
    const container = document.createElement('div');
    const fragment = document.createDocumentFragment();
    for (const { icon, describe, align } of menus) {
      const container = document.createElement('span');
      container.innerHTML = icon;
      container.setAttribute('data-align', align);
      container.classList.add('ql-table-tooltip-hover');
      if (this.options.attribute[propertyName] === align) {
        container.classList.add('ql-table-btns-checked');
      }
      const tooltip = createTooltip(describe);
      container.appendChild(tooltip);
      fragment.appendChild(container);
    }
    container.classList.add('ql-table-check-container');
    container.appendChild(fragment);
    container.addEventListener('click', e => {
      const target: HTMLSpanElement = (
        e.target as HTMLElement
      ).closest('span.ql-table-tooltip-hover');
      const value = target.getAttribute('data-align');
      this.switchButton(container, target);
      this.setAttribute(propertyName, value);
    });
    return container;
  }

  createColorContainer(child: Child) {
    const container = document.createElement('div');
    container.classList.add('ql-table-color-container');
    const input = this.createColorInput(child);
    const colorPicker = this.createColorPicker(child);
    container.appendChild(input);
    container.appendChild(colorPicker);
    return container;
  }

  createColorInput(child: Child) {
    const container = this.createInput(child);
    container.classList.add('label-field-view-color');
    return container;
  }

  createColorList(propertyName: string) {
    const useLanguage = this.getUseLanguage();
    const container = document.createElement('ul');
    const fragment = document.createDocumentFragment();
    container.classList.add('color-list');
    for (const { value, describe } of COLOR_LIST) {
      const li = document.createElement('li');
      const tooltip = createTooltip(useLanguage(describe));
      li.setAttribute('data-color', value);
      li.classList.add('ql-table-tooltip-hover');
      setElementProperty(li, { 'background-color': value });
      li.appendChild(tooltip);
      fragment.appendChild(li);
    }
    container.appendChild(fragment);
    container.addEventListener('click', e => {
      const target = e.target as HTMLLIElement;
      const value = (
        target.tagName === 'DIV'
          ? target.parentElement
          : target
      ).getAttribute('data-color');
      this.setAttribute(propertyName, value, container);
      this.updateInputStatus(container, false, true);
    });
    return container;
  }

  createColorPicker(child: Child) {
    const { propertyName, value } = child;
    const container = document.createElement('span');
    const colorButton = document.createElement('span');
    container.classList.add('color-picker');
    colorButton.classList.add('color-button');
    if (value) {
      setElementProperty(colorButton, { 'background-color': value });
    } else {
      colorButton.classList.add('color-unselected');
    }
    const select = this.createColorPickerSelect(propertyName);
    colorButton.addEventListener('click', () => {
      this.toggleHidden(select);
      const colorContainer = this.getColorClosest(container);
      const input: HTMLInputElement = colorContainer?.querySelector('.property-input');
      this.updateSelectedStatus(select, input?.value, 'color');
    });
    container.appendChild(colorButton);
    container.appendChild(select);
    return container;
  }

  createColorPickerIcon(svg: string, text: string, listener: EventListener) {
    const container = document.createElement('div');
    const icon = document.createElement('span');
    const button = document.createElement('button');
    icon.innerHTML = svg;
    button.innerText = text;
    button.setAttribute('type', 'button');
    container.classList.add('erase-container');
    container.appendChild(icon);
    container.appendChild(button);
    container.addEventListener('click', listener);
    return container;
  }

  createColorPickerSelect(propertyName: string) {
    const useLanguage = this.getUseLanguage();
    const container = document.createElement('div');
    const remove = this.createColorPickerIcon(
      eraseIcon,
      useLanguage('removeColor'),
      () => {
        this.setAttribute(propertyName, '', container);
        this.updateInputStatus(container, false, true);
      }
    );
    const list = this.createColorList(propertyName);
    const palette = this.createPalette(propertyName, useLanguage, container);
    container.classList.add('color-picker-select', 'ql-hidden');
    container.appendChild(remove);
    container.appendChild(list);
    container.appendChild(palette);
    return container;
  }

  createDropdown(value: string, category?: string) {
    const container = document.createElement('div');
    const dropText = document.createElement('span');
    const dropDown = document.createElement('span');
    switch (category) {
      case 'dropdown':
        dropDown.innerHTML = downIcon;
        dropDown.classList.add('ql-table-dropdown-icon');
        break;
      case 'color':
        break;
      default:
        break;
    }
    value && (dropText.innerText = value);
    container.classList.add('ql-table-dropdown-properties');
    dropText.classList.add('ql-table-dropdown-text');
    container.appendChild(dropText);
    if (category === 'dropdown') container.appendChild(dropDown);
    return { dropdown: container, dropText };
  }

  createInput(child: Child) {
    const { attribute, message, propertyName, value, valid } = child;
    const { placeholder = '' } = attribute;
    const container = document.createElement('div');
    const wrapper = document.createElement('div');
    const label = document.createElement('label');
    const input = document.createElement('input');
    const status = document.createElement('div');
    container.classList.add('label-field-view');
    wrapper.classList.add('label-field-view-input-wrapper');
    label.innerText = placeholder;
    setElementAttribute(input, attribute);
    input.classList.add('property-input');
    input.value = value;
    input.addEventListener('input', e => {
      // debounce
      const value = (e.target as HTMLInputElement).value;
      valid && this.switchHidden(status, valid(value));
      this.updateInputStatus(wrapper, valid && !valid(value));
      this.setAttribute(propertyName, value, container);
    });
    status.classList.add('label-field-view-status', 'ql-hidden');
    message && (status.innerText = message);
    wrapper.appendChild(input);
    wrapper.appendChild(label);
    container.appendChild(wrapper);
    valid && container.appendChild(status);
    return container;
  }

  createList(child: Child, dropText?: HTMLSpanElement) {
    const { options, propertyName } = child;
    if (!options.length) return null;
    const container = document.createElement('ul');
    for (const option of options) {
      const list = document.createElement('li');
      list.innerText = option;
      container.appendChild(list);
    }
    container.classList.add('ql-table-dropdown-list', 'ql-hidden');
    container.addEventListener('click', e => {
      const value = (e.target as HTMLLIElement).innerText;
      dropText.innerText = value;
      this.toggleBorderDisabled(value);
      this.setAttribute(propertyName, value);
    });
    return container;
  }

  createPalette(propertyName: string, useLanguage: UseLanguageHandler, parent: HTMLElement) {
    const container = document.createElement('div');
    const palette = document.createElement('div');
    const wrap = document.createElement('div');
    const iroContainer = document.createElement('div');
    // @ts-ignore
    const colorPicker = new iro.ColorPicker(iroContainer, {
      width: 110,
      layout: [
        {
          component: iro.ui.Wheel,
          options: {}
        }
      ]
    });
    const eraseContainer = this.createColorPickerIcon(
      paletteIcon,
      useLanguage('colorPicker'),
      () => this.toggleHidden(palette)
    );
    const btns = this.createActionBtns(
      (e: MouseEvent) => {
        const target = (e.target as HTMLElement).closest('button');
        if (!target) return;
        const label = target.getAttribute('label');
        if (label === 'save') {
          this.setAttribute(
            propertyName,
            colorPicker.color.hexString,
            parent
          );
          this.updateInputStatus(container, false, true);
        }
        palette.classList.add('ql-hidden');
        parent.classList.add('ql-hidden');
      },
      false
    );
    palette.classList.add('color-picker-palette', 'ql-hidden');
    wrap.classList.add('color-picker-wrap')
    iroContainer.classList.add('iro-container');
    wrap.appendChild(iroContainer);
    wrap.appendChild(btns);
    palette.appendChild(wrap);
    container.appendChild(eraseContainer);
    container.appendChild(palette);
    return container;
  }

  createProperty(property: Properties) {
    const { content, children } = property;
    const useLanguage = this.getUseLanguage();
    const container = document.createElement('div');
    const label = document.createElement('label');
    label.innerText = content;
    label.classList.add('ql-table-dropdown-label');
    container.classList.add('properties-form-row');
    if (children.length === 1) {
      container.classList.add('properties-form-row-full');
    }
    container.appendChild(label);
    for (const child of children) {
      const node = this.createPropertyChild(child);
      node && container.appendChild(node);
      if (node && content === useLanguage('border')) {
        this.borderForm.push(node);
      }
    }
    return container;
  }

  createPropertyChild(child: Child) {
    const { category, value } = child;
    switch (category) {
      case 'dropdown':
        const { dropdown, dropText } = this.createDropdown(value, category);
        const list = this.createList(child, dropText);
        dropdown.appendChild(list);
        dropdown.addEventListener('click', () => {
          this.toggleHidden(list);
          this.updateSelectedStatus(dropdown, dropText.innerText, 'dropdown');
        });
        return dropdown;
      case 'color':
        const colorContainer = this.createColorContainer(child);
        return colorContainer;
      case 'menus':
        const checkBtns = this.createCheckBtns(child);
        return checkBtns;
      case 'input':
        const input = this.createInput(child);
        return input;
      default:
        break;
    }
  }

  createPropertiesForm(options: Options) {
    const useLanguage = this.getUseLanguage();
    const { title, properties } = getProperties(options, useLanguage);
    const container = document.createElement('div');
    container.classList.add('ql-table-properties-form');
    const header = document.createElement('h2');
    const actions = this.createActionBtns(
      (e: MouseEvent) => {
        const target = (e.target as HTMLElement).closest('button');
        target && this.checkBtnsAction(target.getAttribute('label'));
      },
      true
    );
    header.innerText = title;
    header.classList.add('properties-form-header');
    container.appendChild(header);
    for (const property of properties) {
      const node = this.createProperty(property);
      container.appendChild(node);
    }
    container.appendChild(actions);
    this.setBorderDisabled();
    this.tableMenus.quill.container.appendChild(container);
    this.updatePropertiesForm(container, options.type);
    this.setSaveButton(actions);
    container.addEventListener('click', (e: MouseEvent) => {
      const target = e.target as HTMLElement;
      this.hiddenSelectList(target);
    });
    return container;
  }

  getCellStyle(td: Element, attrs: Props) {
    const style = (td.getAttribute('style') || '')
      .split(';')
      .filter((value: string) => value.trim())
      .reduce((style: Props, value: string) => {
        const arr = value.split(':');
        return { ...style, [arr[0].trim()]: arr[1].trim() };
      }, {});
    Object.assign(style, attrs);
    return Object.keys(style).reduce((value: string, key: string) => {
      return value += `${key}: ${style[key]}; `;
    }, '');
  }

  getColorClosest(container: HTMLElement) {
    return getClosestElement(container, '.ql-table-color-container');
  }

  getComputeBounds(type: string) {
    if (type === 'table') {
      const { table } = this.tableMenus;
      const [tableBounds, containerBounds] = this.tableMenus.getCorrectBounds(table);
      if (tableBounds.bottom > containerBounds.bottom) {
        return { ...tableBounds, bottom: containerBounds.height };
      }
      return tableBounds;
    } else {
      const { computeBounds } = this.tableMenus.getSelectedTdsInfo();
      return computeBounds;
    }
  }

  getDiffProperties() {
    const change = this.attrs;
    const old = this.options.attribute;
    return Object.keys(change).reduce((attrs: Props, key) => {
      if (change[key] !== old[key]) {
        attrs[key] =
          isDimensions(key)
            ? addDimensionsUnit(change[key])
            : change[key];
      }
      return attrs;
    }, {});
  }

  getUseLanguage() {
    const { language } = this.tableMenus.tableBetter;
    const useLanguage = language.useLanguage.bind(language);
    return useLanguage;
  }

  getViewportSize() {
    return {
      viewWidth: document.documentElement.clientWidth,
      viewHeight: document.documentElement.clientHeight
    }
  }

  hiddenSelectList(element: HTMLElement) {
    const listClassName = '.ql-table-dropdown-properties';
    const colorClassName = '.color-picker';
    const list = this.form.querySelectorAll('.ql-table-dropdown-list');
    const colorPicker = this.form.querySelectorAll('.color-picker-select');
    for (const node of [...list, ...colorPicker]) {
      if (
        node.closest(listClassName)?.isEqualNode(element.closest(listClassName)) ||
        node.closest(colorClassName)?.isEqualNode(element.closest(colorClassName))
      ) {
        continue;
      }
      node.classList.add('ql-hidden');
    }
  }

  removePropertiesForm() {
    this.form.remove();
    this.borderForm = [];
  }

  saveAction(type: string) {
    switch (type) {
      case 'table':
        this.saveTableAction();
        break;
      default:
        this.saveCellAction();
        break;
    }
  }

  saveCellAction() {
    const { selectedTds } = this.tableMenus.tableBetter.cellSelection;
    const { quill, table } = this.tableMenus;
    const tableBlot = Quill.find(table) as TableContainer;
    const colgroup = tableBlot.colgroup();
    const isPercent = tableBlot.isPercent();
    const attrs = this.getDiffProperties();
    const floatW = parseFloat(attrs['width']);
    const width =
      attrs['width']?.endsWith('%')
        ? floatW * getCorrectContainerWidth() / 100
        : floatW;
    const align = attrs['text-align'];
    align && delete attrs['text-align'];
    const newSelectedTds = [];
    if (colgroup && width) {
      delete attrs['width'];
      const { operateLine } = this.tableMenus.tableBetter;
      const { computeBounds } = this.tableMenus.getSelectedTdsInfo();
      const cols = getComputeSelectedCols(computeBounds, table, quill.container);
      for (const col of cols) {
        operateLine.setColWidth(col as HTMLElement, `${width}`, isPercent);
      }
    }
    for (const td of selectedTds) {
      const tdBlot = Quill.find(td) as TableCell;
      const blotName = tdBlot.statics.blotName;
      const formats = tdBlot.formats()[blotName];
      const style = this.getCellStyle(td, attrs);
      if (align) {
        const _align = align === 'left' ? '' : align;
        tdBlot.children.forEach((child: TableCellBlock | ListContainer | TableHeader) => {
          if (child.statics.blotName === ListContainer.blotName) {
            child.children.forEach((ch: TableList) => {
              ch.format && ch.format('align', _align);
            });
          } else {
            child.format('align', _align);
          }
        });
      }
      const parent = tdBlot.replaceWith(blotName, { ...formats, style }) as TableCell;
      newSelectedTds.push(parent.domNode);
    }
    this.tableMenus.tableBetter.cellSelection.setSelectedTds(newSelectedTds);
    if (!isPercent) this.updateTableWidth(table, tableBlot, isPercent);
  }

  saveTableAction() {
    const { table, tableBetter } = this.tableMenus;
    const temporary = (Quill.find(table) as TableContainer).temporary()?.domNode;
    const td = table.querySelector('td,th');
    const attrs = this.getDiffProperties();
    const align = attrs['align'];
    delete attrs['align'];
    switch (align) {
      case 'center':
        Object.assign(attrs, { 'margin': '0 auto' });
        break;
      case 'left':
        Object.assign(attrs, { 'margin': '' });
        break;
      case 'right':
        Object.assign(attrs, { 'margin-left': 'auto', 'margin-right': '' });
        break;
      default:
        break;
    }
    setElementProperty(temporary || table, attrs);
    tableBetter.cellSelection.setSelected(td);
  }

  setAttribute(propertyName: string, value: string, container?: HTMLElement) {
    this.attrs[propertyName] = value;
    if (propertyName.includes('-color')) {
      this.updateSelectColor(this.getColorClosest(container), value);
    }
  }

  setBorderDisabled() {
    const [borderContainer] = this.borderForm;
    // @ts-ignore
    const borderStyle = borderContainer.querySelector('.ql-table-dropdown-text').innerText;
    this.toggleBorderDisabled(borderStyle);
  }

  setSaveButton(container: HTMLDivElement) {
    const saveButton: HTMLButtonElement = container.querySelector('button[label="save"]');
    this.saveButton = saveButton;
  }

  setSaveButtonDisabled(disabled: boolean) {
    if (!this.saveButton) return;
    if (disabled) {
      this.saveButton.setAttribute('disabled', 'true');
    } else {
      this.saveButton.removeAttribute('disabled');
    }
  }

  switchButton(container: HTMLDivElement, target: HTMLSpanElement) {
    const children = container.querySelectorAll('span.ql-table-tooltip-hover');
    for (const child of children) {
      child.classList.remove('ql-table-btns-checked');
    }
    target.classList.add('ql-table-btns-checked');
  }

  switchHidden(container: HTMLElement, valid: boolean) {
    if (!valid) {
      container.classList.remove('ql-hidden');
    } else {
      container.classList.add('ql-hidden');
    }
  }

  toggleBorderDisabled(value: string) {
    const [, colorContainer, widthContainer] = this.borderForm;
    if (value === 'none' || !value) {
      this.attrs['border-color'] = '';
      this.attrs['border-width'] = '';
      this.updateSelectColor(colorContainer, '');
      this.updateInputValue(widthContainer, '');
      colorContainer.classList.add('ql-table-disabled');
      widthContainer.classList.add('ql-table-disabled');
    } else {
      colorContainer.classList.remove('ql-table-disabled');
      widthContainer.classList.remove('ql-table-disabled');
    }
  }

  toggleHidden(container: HTMLElement) {
    container.classList.toggle('ql-hidden');
  }

  updateInputValue(element: Element, value: string) {
    const input: HTMLInputElement = element.querySelector('.property-input');
    input.value = value;
  }

  updateInputStatus(container: HTMLElement, status: boolean, isColor?: boolean) {
    const closestContainer =
      isColor
        ? this.getColorClosest(container)
        : getClosestElement(container, '.label-field-view');
      const wrapper = closestContainer.querySelector('.label-field-view-input-wrapper');
    if (status) {
      wrapper.classList.add('label-field-view-error');
      this.setSaveButtonDisabled(true);
    } else {
      wrapper.classList.remove('label-field-view-error');
      const wrappers = this.form.querySelectorAll('.label-field-view-error');
      if (!wrappers.length) this.setSaveButtonDisabled(false);
    }
  }

  updatePropertiesForm(container: HTMLElement, type: string) {
    container.classList.remove('ql-table-triangle-none');
    const { height, width } = container.getBoundingClientRect();
    const quillContainer = this.tableMenus.quill.container;
    const containerBounds = getCorrectBounds(quillContainer);
    const { top, left, right, bottom } = this.getComputeBounds(type);
    const { viewHeight } = this.getViewportSize();
    let correctTop = bottom + 10;
    let correctLeft = (left + right - width) >> 1;
    if (correctTop + containerBounds.top + height > viewHeight) {
      correctTop = top - height - 10;
      if (correctTop < 0) {
        correctTop = (containerBounds.height - height) >> 1;
        container.classList.add('ql-table-triangle-none');
      } else {
        container.classList.add('ql-table-triangle-up');
        container.classList.remove('ql-table-triangle-down');
      }
    } else {
      container.classList.add('ql-table-triangle-down');
      container.classList.remove('ql-table-triangle-up');
    }
    if (correctLeft < containerBounds.left) {
      correctLeft = 0;
      container.classList.add('ql-table-triangle-none');
    } else if (correctLeft + width > containerBounds.right) {
      correctLeft = containerBounds.right - width;
      container.classList.add('ql-table-triangle-none');
    }
    setElementProperty(container, {
      left: `${correctLeft}px`,
      top: `${correctTop}px`
    });
  }

  updateSelectColor(element: Element, value: string) {
    const input: HTMLInputElement = element.querySelector('.property-input');
    const colorButton: HTMLElement = element.querySelector('.color-button');
    const colorPickerSelect: HTMLElement = element.querySelector('.color-picker-select');
    const status: HTMLElement = element.querySelector('.label-field-view-status');
    if (!value) {
      colorButton.classList.add('color-unselected');
    } else {
      colorButton.classList.remove('color-unselected');
    }
    input.value = value;
    setElementProperty(colorButton, { 'background-color': value });
    colorPickerSelect.classList.add('ql-hidden');
    this.switchHidden(status, isValidColor(value));
  }

  updateSelectedStatus(container: HTMLDivElement, value: string, type: string) {
    const selectors = type === 'color' ? '.color-list' : '.ql-table-dropdown-list';
    const list = container.querySelector(selectors);
    if (!list) return;
    const lists = Array.from(list.querySelectorAll('li'));
    for (const list of lists) {
      list.classList.remove(`ql-table-${type}-selected`);
    }
    const selected = lists.find(li => {
      const data = type === 'color' ? li.getAttribute('data-color') : li.innerText;
      return data === value;
    });
    selected && selected.classList.add(`ql-table-${type}-selected`);
  }

  updateTableWidth(table: HTMLElement, tableBlot: TableContainer, isPercent: boolean) {
    const temporary = tableBlot.temporary();
    setElementProperty(table, { width: 'auto' });
    const { width } = table.getBoundingClientRect();
    table.style.removeProperty('width');
    setElementProperty(temporary.domNode, {
      width: getCorrectWidth(width, isPercent)
    });
  }
}

export default TablePropertiesForm;